#include "data/entry.h"
#include "database/connection.h"
#include "database/database.h"
#include "functional/functional.h"
#include "media_info/media_info.h"
#include "scanner/base_processor.h"

#include <algorithm>

namespace Tvimeg
{
	BaseProcessor::BaseProcessor(void)
	{
		Database::GetInstance().GetConnection().ExecuteQuery("BEGIN TRANSACTION;");
	}

	BaseProcessor::~BaseProcessor(void)
	{
		Database::GetInstance().GetConnection().ExecuteQuery("COMMIT TRANSACTION;");
	}

	void BaseProcessor::Process(const Entry& entry) const
	{
		// get the database connection
		Connection& connection = Database::GetInstance().GetConnection();

		// get some info from the database
		bool entryExists = false;
		bool updateFile = false;

		// make sure that each file is in the database and is up to date
		const Entry::FilesIterator end = entry.GetFilesEnd();
		for (Entry::FilesIterator file = entry.GetFilesBegin(); file != end; ++file)
		{
			connection.PrepareQuery("SELECT updateDate FROM files WHERE path == ?1;");
			connection.BindText(1, file->GetPath());

			if (connection.NextRow())
			{
				// this file was found in the database, which means the entry has already been added
				entryExists = true;

				// check the stored update time for the file
				if (connection.GetInt(0) < file->GetModificationTime())
				{
					// the file needs to be updated
					updateFile = true;
				}
			}
			else
			{
				// one of the files don't exist, so we need to update
				updateFile = true;
			}

			// finalize the query
			connection.FinalizeQuery();
		}

		if (!entryExists)
		{
			// if the entry doesn't exist, then add it
			Add(entry);
		}
		else if (updateFile)
		{
			// one of the files need to be updated
			Update(entry);
		}
	}

	void BaseProcessor::Add(const Entry& entry) const
	{
		// add the entry to the database
		const int entryId = AddEntry(entry);

		if (entryId != -1)
		{
			// add all the files to the database
			std::vector<int> fileIds;
			std::for_each(entry.GetFilesBegin(), entry.GetFilesEnd(), Bind(*this, &BaseProcessor::AddFile, fileIds));

			// remove any files that may already exist for this entry
			RemoveEntryFiles(entryId);

			// add the entry -> file links
			std::for_each(fileIds.begin(), fileIds.end(), Bind(*this, &BaseProcessor::AddEntryFile, entryId));
		}
	}

	void BaseProcessor::AddFile(const File& file, std::vector<int>& fileIds) const
	{
		// add the file to the files table
		Connection& connection = Database::GetInstance().GetConnection();
		connection.PrepareQuery("INSERT INTO files (path, updateDate, containerFormat, duration, videoFormat, width, height, aspectRatio, frameRate, audioFormat, channels) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11);");
		connection.BindText(1, file.GetPath());
		connection.BindInt(2, file.GetModificationTime());

		MediaInfo info(file);
		connection.BindText(3, info.GetContainerFormat());
		connection.BindInt(4, info.GetDuration());
		connection.BindText(5, info.GetVideoFormat());
		connection.BindInt(6, info.GetWidth());
		connection.BindInt(7, info.GetHeight());
		connection.BindFloat(8, info.GetAspectRatio());
		connection.BindFloat(9, info.GetFrameRate());
		connection.BindText(10, info.GetAudioFormat());
		connection.BindInt(11, info.GetChannels());

		if (connection.ExecuteQuery())
		{
			fileIds.push_back(connection.GetLastId());
		}
	}

	void BaseProcessor::Update(const Entry& entry) const
	{
		// update the entry
		const int entryId = UpdateEntry(entry);

		if (entryId != -1)
		{
			// update all the files in the database
			std::vector<int> fileIds;
			std::for_each(entry.GetFilesBegin(), entry.GetFilesEnd(), Bind(*this, &BaseProcessor::UpdateFile, fileIds));

			// remove any files that may already exist for this entry
			RemoveEntryFiles(entryId);

			// add the entry -> file links
			std::for_each(fileIds.begin(), fileIds.end(), Bind(*this, &BaseProcessor::AddEntryFile, entryId));
		}
	}

	void BaseProcessor::UpdateFile(const File& file, std::vector<int>& fileIds) const
	{
		// try to find the file in the database
		Connection& connection = Database::GetInstance().GetConnection();
		connection.PrepareQuery("SELECT id FROM files WHERE path = ?1;");
		connection.BindText(1, file.GetPath());
		if (connection.NextRow())
		{
			// add the file id to the list
			const int fileId = connection.GetInt(0);
			fileIds.push_back(fileId);

			// update the file info
			connection.PrepareQuery("UPDATE files SET updateDate = ?1, containerFormat = ?2, duration = ?3, videoFormat = ?4, width = ?5, height = ?6, aspectRatio = ?7, frameRate = ?8, audioFormat = ?9, channels = ?10 WHERE id = ?11;");
			connection.BindInt(2, file.GetModificationTime());
			connection.BindInt(11, fileId);

			MediaInfo info(file);
			connection.BindText(2, info.GetContainerFormat());
			connection.BindInt(3, info.GetDuration());
			connection.BindText(4, info.GetVideoFormat());
			connection.BindInt(5, info.GetWidth());
			connection.BindInt(6, info.GetHeight());
			connection.BindFloat(7, info.GetAspectRatio());
			connection.BindFloat(8, info.GetFrameRate());
			connection.BindText(9, info.GetAudioFormat());
			connection.BindInt(10, info.GetChannels());

			connection.ExecuteQuery();
		}
		else
		{
			// the file wasn't in the database, so add it
			AddFile(file, fileIds);
		}
	}
}
